package com.dncompute.marrow;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 
 *  Make sure you add the following into your "web.xml" file:
 *  <filter>
 *		<filter-name>dispatchfilter</filter-name>
 *		<filter-class>com.dncompute.marrow.DispatcherFilter</filter-class>
 *	</filter>
 *	<filter-mapping>
 *       <filter-name>dispatchfilter</filter-name>
 *       <url-pattern>/*</url-pattern>
 *   </filter-mapping>
 *   
 * @author noelb
 *
 */
public class DispatcherFilter implements Filter {
	
	
	public enum ServerEnv { UNKNOWN, DEV, PROD };
	public static ServerEnv environment = ServerEnv.UNKNOWN;
	
	private static final String CONTROLLER_PACKAGE_NAME = "controller";
	protected FilterConfig config;
	
	
	@Override
	public void init(FilterConfig config) throws ServletException {
		this.config = config;	
	}	
	
	
	@SuppressWarnings("unchecked")
	@Override
	public void doFilter(ServletRequest sreq, ServletResponse sres, FilterChain chain) throws IOException, ServletException {
		
		HttpServletRequest req = (HttpServletRequest) sreq;
		HttpServletResponse res = (HttpServletResponse) sres;
		
		try {
			
			//Get URL path as an ArrayList
			String pathAry[] = req.getRequestURI().substring(1).split("/");
			for (int i=0; i < pathAry.length; i++) pathAry[i] = URLDecoder.decode(pathAry[i],"UTF-8");
			ArrayList<String> path = new ArrayList<String>( Arrays.asList(pathAry) );
			
			//Discover the controller class based on the path
			Class<Controller> classDefinition = null;
			String packageName = CONTROLLER_PACKAGE_NAME;
			while (path.size() > 0 && classDefinition == null) {
				String pathItem = path.remove(0);
				String controllerName = packageName+"."+ initialCap( pathItem );
				try {
					classDefinition = (Class<Controller>) Class.forName(controllerName);
				} catch(ClassNotFoundException e) {}
				if (classDefinition == null) packageName += "."+pathItem;
			}
			
			//If we were unable to discover the class name, let the request pass through as normal
			if (classDefinition == null) {
				chain.doFilter(req, res);
				return;
			}
			
			//Create the new controller
			Controller controller = classDefinition.newInstance();
	        controller.req = req;
			controller.res = res;
			
			//Set a flag to determine if we are on dev or prod that can be used in Controllers
			if (DispatcherFilter.environment == ServerEnv.UNKNOWN) {
				DispatcherFilter.environment = 
						config.getServletContext().getServerInfo().startsWith("Google App Engine Development") ? 
						ServerEnv.DEV : ServerEnv.PROD;
			}
			
			//If we've defined a handler for preprocessing the request
			boolean consumedRequest = controller.preprocess(path);
			if (consumedRequest) return;
			
	        //Dispatch to appropriate sub-method
			ArrayList<String> subpath = (ArrayList<String>) path.clone();
			String methodName = (path.size() == 0) ? "index" : subpath.remove(0);
			Object methodArg = null;
			if (subpath.size() == 1) methodArg = subpath.get(0);
			else if (subpath.size() > 1) methodArg = subpath;
			boolean invokeSuccess = invokeMethod(controller, methodName, methodArg);
			
			//If we failed to invoke, try an attempt at a generalized index handler before giving up
			if (!invokeSuccess) {
				invokeSuccess = invokeMethod(controller,"index",path);
				if (!invokeSuccess) {
					chain.doFilter(req, res);
					return;
				} 
			}
		
		} catch (InstantiationException e) {
			outputError(e);
		} catch (IllegalAccessException e) {
			outputError(e);
		}
		
	}
	
	
	private void outputError(Exception e) {
		e.printStackTrace();
	}
	
	
	private boolean invokeMethod(Controller controller, String name, Object methodArg) {
		try {
			if (methodArg == null) {
				Object[] args = new Object[]{};
				Method method = controller.getClass().getMethod(name);
				method.invoke(controller,args);	
			} else {
				Object[] args = new Object[]{methodArg};
				Method method = controller.getClass().getMethod(name,methodArg.getClass());
				method.invoke(controller,args);
			}
			return true;
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace(); //FIXME IS THIS RIGHT?
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		return false;
	}
	
	
	//Util method
	private static String initialCap(String str) {
		return str.substring(0, 1).toUpperCase() + str.substring(1, str.length());
	}
	
	
	@Override
	public void destroy() {	}
	
}
